package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.cache.service.CacheService;
import com.atguigu.gmall.common.consts.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.feign.list.SearchFeignClient;
import com.atguigu.gmall.feign.product.ProductFeignClient;
import com.atguigu.gmall.cache.aop.annotation.Cache;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.model.product.SpuSaleAttr;
import com.atguigu.gmall.model.to.SkuDetailTo;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author 卡尼奶
 * @Date 2022/5/22 17:26
 * @Version 1.0
 */
@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {


    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private SearchFeignClient searchFeignClient;

    @Autowired
    private ThreadPoolExecutor poolExecutor;

    @Autowired
    private CacheService cacheService;

    @Autowired
    private RBloomFilter<Object> bloomFilter;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Cache
    @Override
    public SkuDetailTo getSkuDetail(Long skuId) {
        return getSkuDetailCache(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        Double score = redisTemplate.opsForZSet().incrementScore(RedisConst.SKU_DETAIL_HOT, skuId.toString(), 1.0);
        if (score % 100 == 0){
            searchFeignClient.incrHotScore(skuId,score.longValue());
        }
    }

    public SkuDetailTo getSkuDetailLock(Long skuId) {
        String key = RedisConst.SKU_CACHE_PREFIX + skuId;
        SkuDetailTo cacheData = cacheService.getCache(key, new TypeReference<SkuDetailTo>() {
        });
        if (cacheData == null) {
            log.info("SkuDetail：{} 缓存没有，准备回源", skuId);
            //先去布隆里查询是否存在
            boolean contains = bloomFilter.contains(skuId);
            if (contains) {
                log.info("SkuDetail：{} 布隆命中", skuId);
                RLock lock = redissonClient.getLock(RedisConst.SKU_DETAIL_LOCK_PREFIX + skuId);
                boolean tryLock = lock.tryLock();
                SkuDetailTo detail = null;
                try {
                    if (tryLock) {
                        log.info("SkuDetail：{} 加锁成功", skuId);
                        //去数据库中查询
                        detail = getSkuDetailCache(skuId);
                        //保存到缓存中
                        cacheService.setCache(key, detail);
                        return detail;
                    }
                    log.info("SkuDetail：{} 加锁失败", skuId);
                    Thread.sleep(1000);
                    cacheData = cacheService.getCache(key, new TypeReference<SkuDetailTo>() {
                    });
                    return cacheData;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //解锁
                    log.info("SkuDetail：{} 解锁成功", skuId);
                    if (lock.isLocked()) {
                        lock.unlock();
                    }
                }
            }
            log.info("SkuDetail：{} 布隆没有，返回Null", skuId);
            return null;
        }
        return cacheData;
    }


    public SkuDetailTo getSkuDetailWith(Long skuId) {
        String key = RedisConst.SKU_CACHE_PREFIX + skuId;
        SkuDetailTo cacheData = cacheService.getCache(key, new TypeReference<SkuDetailTo>() {
        });
        //如果缓存中没有数据，就先去布隆中查询这个商品ID是否存在，如果存在，就去数据库中查询
        if (cacheData == null) {
            //先去布隆里查询是否存在
            boolean contains = bloomFilter.contains(skuId);

            if (contains) {
                String token = UUID.randomUUID().toString();
                log.info("Sku {} 缓存没命中..正在去数据库中查询...", skuId);
                Boolean lock = redisTemplate.opsForValue().setIfAbsent(RedisConst.LOCK_PREFIX + skuId, token, 10, TimeUnit.SECONDS);
                SkuDetailTo detail = null;
                if (lock) {
                    try {
                        //去数据库中查询
                        detail = getSkuDetailCache(skuId);
                        //保存到缓存中
                        cacheService.setCache(key, detail);
                    } finally {
                        //释放锁
                        String value = redisTemplate.opsForValue().get(RedisConst.LOCK_PREFIX + skuId);
                        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                        Long lock1 = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList("lock"), token);

                        if (lock1.intValue() == 1) {
                            log.info("分布式解锁完成");
                            redisTemplate.delete("lock");
                        } else {
                            log.info("分布式锁，不能删");
                        }
                    }
                } else {
                    try {
                        Thread.sleep(1000);
                        cacheData = cacheService.getCache(key, new TypeReference<SkuDetailTo>() {
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return cacheData;
                }
                return detail;
            }
            log.info("Sku {} 缓存没命中..布隆过滤器拦截..压根没这个商品..", skuId);
            return null;
        }
        log.info("Sku {} 缓存命中...", skuId);
        return cacheData;
    }

    //商品详情服务:
    //查询sku详情得做这么多式
    // 1、查分类
    //2、查Sku信息
    // 3、查价格
    //4、查所有销售属性组合
    // 5、查实际sku组合
//    @Override
    public SkuDetailTo getSkuDetailCache(Long skuId) {
        SkuDetailTo skuDetailTo = new SkuDetailTo();
        // 1、查分类
        CompletableFuture<Void> categoryTask = CompletableFuture.runAsync(() -> {
            Result<BaseCategoryView> skuCategoryView = productFeignClient.getSkuCategoryView(skuId);
            if (skuCategoryView.isOk()) {
                skuDetailTo.setCategoryView(skuCategoryView.getData());
            }
        }, poolExecutor);

        //2、查Sku信息
        CompletableFuture<Void> skuInfoTask = CompletableFuture.runAsync(() -> {
            Result<SkuInfo> skuInfo = productFeignClient.getSkuInfo(skuId);
            if (skuInfo.isOk()) {
                skuDetailTo.setSkuInfo(skuInfo.getData());
            }
        }, poolExecutor);

        // 3、查价格
        CompletableFuture<Void> priceTask = CompletableFuture.runAsync(() -> {
            Result<BigDecimal> price = productFeignClient.getPrice(skuId);
            if (price.isOk()) {
                skuDetailTo.setPrice(price.getData());
            }
        }, poolExecutor);

        //4、查所有销售属性组合
        CompletableFuture<Void> attrAndValueTask = CompletableFuture.runAsync(() -> {
            Result<List<SpuSaleAttr>> attrAndValue = productFeignClient.getSkuSaleAttrAndValue(skuId);
            if (attrAndValue.isOk()) {
                skuDetailTo.setSpuSaleAttrList(attrAndValue.getData());
            }
        }, poolExecutor);

        // 5、查实际sku组合
        CompletableFuture<Void> valuesSkuJsonTask = CompletableFuture.runAsync(() -> {
            Result<Map<String, String>> valuesSkuJson =
                    productFeignClient.getValuesSkuJson(skuId);
            if (valuesSkuJson.isOk()) {
                skuDetailTo.setValueSkuJson(valuesSkuJson.getData());
            }
        }, poolExecutor);

        CompletableFuture.allOf(categoryTask, skuInfoTask, priceTask, attrAndValueTask, valuesSkuJsonTask).join();

        return skuDetailTo;
    }
}
